/*
Pyxis 2.0
Copyright 2010-2011 Thomas W. Holtquist

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

using System;
using System.Collections;
using System.Ext.Xml;
using System.IO;
using System.Text;
using System.Threading;
using System.Xml;

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT.Presentation.Media;
using Microsoft.SPOT.Touch;
using Microsoft.SPOT.Time;

using GHIElectronics.NETMF.Hardware;
using GHIElectronics.NETMF.System;

using Skewworks.Pyxis.EXT;
using Skewworks.Pyxis.GUI.Controls;

using FastloadMedia.NETMF.Net;

namespace Skewworks.Pyxis.Kernel
{

    #region Event Delegates

    [Serializable]
    public delegate void OnButtonPressed(PyxisButton Button, ButtonState State);

    [Serializable]
    public delegate void OnModalStateChanged(bool IsModalActive);

    [Serializable]
    public delegate void OnDateTimeChanged();

    [Serializable]
    public delegate void OnUnhandledTouch(bool Down, point e);

    #endregion

    public class PyxisAPI : MarshalByRefObject
    {

        #region Constants

        public static readonly string Product = "Pyxis";
        public static readonly string Version = "2.0.1.0";
        public static readonly string Copyright = "2010 - 2011 Thomas W. Holtquist";
        public static readonly int BuildDate = 20110422;

        #endregion

        #region Variables

        internal Bitmap _buffer;                                // Main Bitmap that gets flushed to the LCD

        protected internal Form _ActiveForm = null;             // Currently active Form
        protected internal ContextMenu _ActiveCM = null;        // Currently active Context Menu

        private InputManager IM;                                // Input Manager
        private DriveManager DM;                                // Drive Manager
        private USBManager UM;                                  // USB Manager
        private NetworkManager NM;                              // Network Manager
        private FileManager FM;                                 // FileManager

        private ArrayList _blockers = new ArrayList();          // List of ManualResetEvents for blocking
        private ManualResetEvent _activeBlock = null;           // Used to display Modal Forms
        private PromptResult _promptResult;                     // Used by Prompt
        private Thread myTouch;                                 // Gathers touches during Modal display
        private string _inputResult = String.Empty;             // Houses result of all input gathering Modals
        private Color _colorResult;                             // Result from Color Selection Dialog
        private int _selResult;
        private static bool _IsSave;                            // Open/Save Dialogs
        private static bool _CheckExist;                        // Save Dialog (don't overwrite)

        internal Desktop desktop;                               // Desktop Instance
        internal ArrayList _runningApps = new ArrayList();      // ArrayList of running Applications
        internal ArrayList _services = new ArrayList();         // ArrayList of all installed Services

        private Thread _launcher;                               // App Launching Thread
        private string _sRelay;                                 // Used for Launching apps
        private string _sTitle;                                 // Used for Launching apps
        private string[] _sParams;                              // Parameters for Application Launch
        private PyxisApplication _PAQuitter;                    // Used for quitting apps
        private object _KeyQuitter;                             // Used for quitting apps & services

        #endregion

        #region Events

        public event OnModalStateChanged ModalStateChanged;

        /// <summary>
        /// Fired when modal state changes
        /// </summary>
        /// <param name="IsModalActive"></param>
        protected virtual void OnModalStateChanged(bool IsModalActive)
        {
            if (ModalStateChanged != null)
                ModalStateChanged(IsModalActive);
        }

        public event OnDateTimeChanged DateTimeChanged;

        /// <summary>
        /// Fired when date/time changes
        /// </summary>
        protected virtual void OnDateTimeChanged()
        {
            if (DateTimeChanged != null)
                DateTimeChanged();
        }

        public event OnUnhandledTouch UnhandledTouch;

        /// <summary>
        /// Fires when a touch is received but unused
        /// </summary>
        /// <param name="Down"></param>
        /// <param name="e"></param>
        protected virtual void OnUnhandledTouch(bool Down, point e)
        {
            if (UnhandledTouch != null)
                UnhandledTouch(Down, e);
        }

        #endregion

        #region Constructor

        public PyxisAPI()
        {
            
            // Create Managers
            IM = new InputManager(this, AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight);
            DM = new DriveManager();
            UM = new USBManager(this);
            NM = new NetworkManager(this);
            FM = new FileManager(this);

            // Restore Time Zone
            TimeService.SetTimeZoneOffset(GetTimeZoneMinutes(SettingsManager.BootSettings.TimeZone));

            // Create Buffer
            _buffer = new Bitmap(AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight);
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets/Sets the Active Form
        /// </summary>
        public Form ActiveForm
        {
            get { return _ActiveForm; }
            set
            {
                if (_ActiveForm == value) return;
                _ActiveForm = value;
                if (_ActiveForm != null)
                    _ActiveForm.Render();
                else if (desktop != null)
                    desktop.Render(true);
            }
        }

        /// <summary>
        /// Returns the current amount of Free RAM
        /// Also performs a Garbage Collection
        /// </summary>
        /// <param name="FormatOutput">Formats RAM if true</param>
        /// <returns></returns>
        public static string FreeRAM(bool FormatOutput)
        {
            string[] sSize = new string[] { " bytes", " KB", " MB", "GB", " TB" };
            byte index = 0;
            float iFree = Debug.GC(false);
            string sFree = string.Empty;

            // Unformatted
            if (!FormatOutput)
                return iFree.ToString();

            // Format
            while (iFree > 1024)
            {
                iFree = iFree / 1024;
                index++;
                if (index == 4)
                    break;
            }

            sFree = iFree.ToString();
            if (sFree.IndexOf('.') > 0)
                sFree = sFree.Substring(0, sFree.IndexOf('.') + 3);
            return sFree + sSize[index];
        }

        /// <summary>
        /// Returns the Drive Manager
        /// </summary>
        public DriveManager MyDrives
        {
            get { return DM; }
        }

        /// <summary>
        /// Returns File Manager instance
        /// </summary>
        public FileManager MyFiles
        {
            get { return FM; }
        }

        /// <summary>
        /// Returns Network Manager instance
        /// </summary>
        public NetworkManager MyNetwork
        {
            get { return NM; }
        }

        /// <summary>
        /// Returns the USB Manager
        /// </summary>
        public USBManager MyUSB
        {
            get { return UM; }
        }

        /// <summary>
        /// Gets a reference to the main Bitmap
        /// </summary>
        internal Bitmap ScreenBuffer
        {
            get { return _buffer; }
        }

        /// <summary>
        /// Returns the Menu Tray
        /// </summary>
        public MenuTray SystemMenuTray
        {
            get { return desktop._mnu._tray; }
        }

        /// <summary>
        /// Returns true if the Virtual Keyboard should be used
        /// </summary>
        public bool UseVirtualKeyboard
        {
            get { return IM.UseVirtualKeyboard; }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Causes Pyxis to calibrate the Touch Screen
        /// </summary>
        public void CalibrateScreen()
        {
            if (SystemInfo.SystemID.SKU == 3) return;

            // Start Modal Touch Collection
            myTouch = new Thread(myCalibrate_Tick);
            myTouch.Priority = ThreadPriority.Highest;
            myTouch.Start();

            ModalBlock();
        }

        /// <summary>
        /// Checks for updates to the system
        /// </summary>
        /// <param name="Silently">Works in background if true</param>
        public void CheckForUpdates(bool Silently)
        {
            // Steal the prompt thread for checking updates
            // use _IsSave to house silently
            // All about keeping the resources down
            myTouch = new Thread(UpdateThread);
            myTouch.Priority = ThreadPriority.Highest;
            _IsSave = Silently;
            myTouch.Start();
        }

        /// <summary>
        /// Creates a link to a specified application
        /// </summary>
        /// <param name="path">Full path to the application</param>
        /// <returns></returns>
        public byte[] CreateLink(string path)
        {
            if (!File.Exists(path))
                throw new Exception("Path does not exist");

            return CreateLink(File.ReadAllBytes(path), path);
        }

        /// <summary>
        /// Activates the Desktop
        /// </summary>
        public void DisplayDesktop()
        {
            // Create Desktop if it doesn't already exist
            if (desktop == null)
            {
                desktop = new Desktop(this);
                Thread LS = new Thread(LoadServices);
                LS.Priority = ThreadPriority.BelowNormal;
                LS.Start();
            }

            _ActiveForm = null;
            desktop.Activate();
        }

        public PyxisService[] GetInstalledServices()
        {
            PyxisService[] svc = new PyxisService[_services.Count];
            for (int i = 0; i < svc.Length; i++)
                svc[i] = (PyxisService)_services[i];
            return svc;
        }

        public PyxisService[] GetRunningServices()
        {
            PyxisService PS;
            PyxisService[] holder, ret;
            int c = 0;
            int i;

            holder = new PyxisService[_services.Count];
            for (i = 0; i < _services.Count; i++)
            {
                PS = (PyxisService)_services[i];
                if (PS.Info.IsRunning)
                    holder[c++] = PS;
            }

            ret = new PyxisService[c];
            Array.Copy(holder, ret, ret.Length);
            return ret;
        }

        public PyxisService[] GetServiceByStartMode(StartMode StartMode)
        {
            PyxisService PS;
            PyxisService[] holder, ret;
            int c = 0;
            int i;

            holder = new PyxisService[_services.Count];
            for (i = 0; i < _services.Count; i++)
            {
                PS = (PyxisService)_services[i];
                if (PS.Info.StartMode == StartMode)
                    holder[c++] = PS;
            }

            ret = new PyxisService[c];
            Array.Copy(holder, ret, ret.Length);
            return ret;
        }

        public PyxisService[] GetStoppedServices()
        {
            PyxisService PS;
            PyxisService[] holder, ret;
            int c = 0;
            int i;

            holder = new PyxisService[_services.Count];
            for (i = 0; i < _services.Count; i++)
            {
                PS = (PyxisService)_services[i];
                if (!PS.Info.IsRunning)
                    holder[c++] = PS;
            }

            ret = new PyxisService[c];
            Array.Copy(holder, ret, ret.Length);
            return ret;
        }

        /// <summary>
        /// Hides any shown Context Menu
        /// </summary>
        public void HideContextMenu()
        {
            if (_ActiveCM == null)
                return;

            _ActiveCM = null;
            RefreshScreen();
        }

        /// <summary>
        /// Returns a bitmap from raw bytes
        /// Null if unsupported image
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static Bitmap ImageFromBytes(byte[] data)
        {
            if (data[0] == 0xFF && data[1] == 0xD8)
            {
                // JPEG
                return new Bitmap(data, Bitmap.BitmapImageType.Jpeg);
            }

            if (data[0] == 0x42 && data[1] == 0x4D)
            {
                // BMP
                return new Bitmap(data, Bitmap.BitmapImageType.Bmp);
            }

            if (data[0] == 0x47 && data[1] == 0x49 && data[2] == 0x46 && data[3] == 0x38)
            {
                // GIF
                return new Bitmap(data, Bitmap.BitmapImageType.Gif);
            }

            return null;
        }

        /// <summary>
        /// Gathers input from the user
        /// </summary>
        /// <param name="Message">Message to display the user</param>
        /// <param name="Title">Title to display in the window</param>
        /// <returns></returns>
        public string Inputbox(string Message, string Title)
        {
            return Inputbox(Message, Title, string.Empty);
        }

        /// <summary>
        /// Gathers input from the user
        /// </summary>
        /// <param name="Message">Message to display the user</param>
        /// <param name="Title">Title to display in the window</param>
        /// <param name="DefaultReponse">Default Response for the user</param>
        /// <returns></returns>
        public string Inputbox(string Message, string Title, string DefaultReponse)
        {
            int w, h, x, y, i;

            // Copy out the current buffer
            Bitmap buffer2 = new Bitmap(AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight);
            buffer2.DrawImage(0, 0, _buffer, 0, 0, buffer2.Width, buffer2.Height);

            // First figure out how wide to make the window
            FontManager.Arial.ComputeExtent(Title, out w, out h);
            size sz = FontManager.ComputeExtentEx(FontManager.Arial, Message);
            if (sz.Width > w) w = sz.Width;

            // Add border & CommandButtons
            w += 96;

            // Now make sure we're within the screen width
            if (w > AppearanceManager.ScreenWidth - 8) w = AppearanceManager.ScreenWidth - 8;

            // Get the height of the 2 strings combined
            // We already have the title height in h
            // Remove 84px for borders, spacing and CommandButtons
            FontManager.Arial.ComputeTextInRect(Message, out y, out i, w - 96);
            h = h + i;
            if (h < 54) h = 54; // Minimum height of OK/Cancel CommandButtons

            // Add borders and spacing 49px (4 border + 2 space + title + 2 space + 4 border = 4 space + text height + 4 space + Textbox height + 4 space + 4 border)
            h += 69;

            // Make sure we're still on the screen
            if (h > AppearanceManager.ScreenHeight - 8) h = AppearanceManager.ScreenHeight - 8;

            // Center the window
            x = (int)(((float)AppearanceManager.ScreenWidth / 2) - ((float)w / 2));
            y = (int)(((float)AppearanceManager.ScreenHeight / 2) - ((float)h / 2));

            // We're ready to create the PromptWindow
            Form pw = new Form(this, Colors.LightGray, x, y, w, h, true, true, Form.WindowType.fullborder);
            pw.AddChild(new Label(Title, Color.White, 6, 5, w - 10, FontManager.Arial.Height));
            pw.AddChild(new Label(Message, Color.Black, 8, 5 + FontManager.Arial.Height + 6, w - 96, h - 69 - FontManager.Arial.Height));

            // Add Textbox
            Textbox txt1 = new Textbox(DefaultReponse, 8, pw.Height - 28, pw.Width - 16, 20);
            txt1.VirtualkeyboardClosed += new OnVirtualKeyboardClosed((object sender) => InputFixVKeyboard(buffer2));
            pw.AddChild(txt1);

            // Add CommandButtons
            CommandButton btnOK = new CommandButton("OK", pw.Width - 80, 5 + FontManager.Arial.Height + 6);
            btnOK.tapEvent += new OnTap((object sender, point e) =>  Input_CommandButtonClicked(txt1.Text));
            pw.AddChild(btnOK);
            CommandButton btnCancel = new CommandButton("Cancel", pw.Width - 80, btnOK.Y + 29);
            btnCancel.tapEvent += new OnTap((object sender, point e) =>  Input_CommandButtonClicked(""));
            pw.AddChild(btnCancel);

            // Set the Active Form (without rendering)
            Form prv = _ActiveForm;
            _ActiveForm = pw;

            // Draw Prompt
            _buffer.DrawRectangle(Color.Black, 0, 0, 0, buffer2.Width, buffer2.Height, 0, 0, Color.Black, 0, 0, Color.Black, 0, 0, 100);
            _buffer.Flush();
            pw.Render();

            // Start Modal Touch Collection
            myTouch = new Thread(myTouch_Tick);
            myTouch.Priority = ThreadPriority.Highest;
            myTouch.Start();

            ModalBlock();

            // Restore Screen
            _ActiveForm = prv;
            _buffer.SetClippingRectangle(0, 0, buffer2.Width, buffer2.Height);
            _buffer.DrawImage(0, 0, buffer2, 0, 0, buffer2.Width, buffer2.Height);
            _buffer.Flush();

            // Return result
            return _inputResult;
        }

        /// <summary>
        /// Installs the specified application install file
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        public bool InstallApplication(string filename)
        {
            byte[] b;
            FileStream iFile, iWrite;
            string sDir, sAppName, sName, sPath;
            string sPXE = string.Empty;
            int iLen, iCount, i;
            bool bLaunchService = false;

            // Check file
            if (!File.Exists(filename))
            {
                Prompt("Could not install " + Path.GetFileName(filename) + ".\r\nFile not found.", Product, PromptType.OKOnly);
                return false;
            }

            // Check Magic Number
            iFile = new FileStream(filename, FileMode.Open, FileAccess.Read);
            b = new byte[4];
            iFile.Read(b, 0, 4);
            if (b[0] != 112 || b[1] != 105 || b[2] != 101 || b[3] != 95)
            {
                Prompt("Could not install " + Path.GetFileName(filename) + ".\r\nInvalid install file.", Product, PromptType.OKOnly);
                return false;
            }

            // Read Application Name
            iFile.Read(b, 0, 1);
            b = new byte[b[0]];
            iFile.Read(b, 0, b.Length);
            sAppName = new string(UTF8Encoding.UTF8.GetChars(b));

            // Get an install directory
            sDir = MyDrives.RootDirectory + "apps\\" + sAppName + "\\";
            if (Directory.Exists(sDir))
            {
                sDir = SelectDirectory(sAppName + " - Select Install Directory");
                if (sDir == string.Empty)
                    return false;
            }
            else
                Directory.CreateDirectory(sDir);

            // Install Root Files
            try
            {
                iFile.Read(b, 0, 1);
                iCount = b[0];          // Check the number of items in the directory
                for (i = 0; i < iCount; i++)
                {
                    // Read file name
                    iFile.Read(b, 0, 1);
                    b = new byte[b[0]];
                    iFile.Read(b, 0, b.Length);
                    sName = new string(UTF8Encoding.UTF8.GetChars(b));

                    // Watch for application
                    if (sPXE == string.Empty && Path.GetExtension(sName.ToLower()) == ".pxe")
                        sPXE = sDir + sName;

                    // Get File Size
                    b = new byte[4];
                    iFile.Read(b, 0, 4);
                    iLen = ((b[3] & 0xFF) << 24) + ((b[2] & 0xFF) << 16) + ((b[1] & 0xFF) << 8) + (b[0] & 0xFF);

                    // Delete existant file
                    if (File.Exists(sDir + sName))
                        File.Delete(sDir + sName);

                    // Write File
                    b = new byte[iLen];
                    iFile.Read(b, 0, b.Length);
                    iWrite = new FileStream(sDir + sName, FileMode.CreateNew, FileAccess.Write);
                    iWrite.Write(b, 0, b.Length);
                    iWrite.Close();
                }
            }
            catch (Exception e)
            {
                Prompt("Install failed!\r\n" + e.Message, Product, PromptType.OKOnly);
                return false;
            }

            // Install Remaining Folders & Files
            try
            {
                while (true)
                {
                    // Read Directory Name or EOF
                    sName = "";
                    iFile.Read(b, 0, 1);
                    if (b[0] == 0)
                        break;
                    b = new byte[b[0]];
                    iFile.Read(b, 0, b.Length);
                    sName = new string(UTF8Encoding.UTF8.GetChars(b));
                    if (sName.Substring(0, 1) == "\\")
                        sName = sName.Substring(1);

                    // Create Directory
                    sPath = FileManager.NormalizeDirectory(sDir + sName);
                    if (!Directory.Exists(sPath)) Directory.CreateDirectory(sPath);

                    // Write files in Directory
                    iFile.Read(b, 0, 1);
                    iCount = b[0];          // Check the number of items in the directory
                    for (i = 0; i < iCount; i++)
                    {
                        // Read file name
                        iFile.Read(b, 0, 1);
                        b = new byte[b[0]];
                        iFile.Read(b, 0, b.Length);
                        sName = new string(UTF8Encoding.UTF8.GetChars(b));

                        // Get File Size
                        b = new byte[4];
                        iFile.Read(b, 0, 4);
                        iLen = ((b[3] & 0xFF) << 24) + ((b[2] & 0xFF) << 16) + ((b[1] & 0xFF) << 8) + (b[0] & 0xFF);

                        // Delete existant file
                        if (File.Exists(sPath + sName))
                            File.Delete(sPath + sName);

                        // Write File
                        b = new byte[iLen];
                        iFile.Read(b, 0, b.Length);
                        iWrite = new FileStream(sPath + sName, FileMode.CreateNew, FileAccess.Write);
                        iWrite.Write(b, 0, b.Length);
                        iWrite.Close();
                    }

                }
            }
            catch (Exception e)
            {
                Prompt("Install failed!\r\n" + e.Message, Product, PromptType.OKOnly);
                return false;
            }

            // Create Associations
            try
            {
                iFile.Read(b, 0, 1);
                iCount = b[0];

                for (i = 0; i < iCount; i++)
                {
                    // Read file ext
                    iFile.Read(b, 0, 1);
                    b = new byte[b[0]];
                    iFile.Read(b, 0, b.Length);
                    sName = new string(UTF8Encoding.UTF8.GetChars(b));

                    MyFiles.AssociateType(sName, sPXE + " %1", "");
                }

                if (iCount > 0)
                    MyFiles.SaveAssociations();

            }
            catch (Exception) { }   // Ignore errors here

            iFile.Close();

            i = AppLoader.GetPXEType(this, sPXE);

            // None
            if (i == -1)
            {
                Prompt("The PXE file does not contain a valid application or service!", Product, PromptType.OKOnly);
                File.Delete(filename);
                MyDrives.FlushFileSystems();
                return false;
            }

            // App Present
            if (i == 0 || i == 2)
            {
                if (sPXE != string.Empty)
                {
                    if (Prompt("Would you like to create a shortcut for this application on the desktop?", sAppName, PromptType.YesNo) == PromptResult.Yes)
                    {
                        try
                        {
                            b = CreateLink(sPXE);
                            string file = MyDrives.RootDirectory + "pyxis\\desktop\\" + PyxisAPI.StringReplace(Path.GetFileName(sPXE), Path.GetExtension(sPXE), "");
                            if (File.Exists(file + ".lnk"))
                                file = FileManager.GetFile(file);

                            FileStream fs = new FileStream(file + ".lnk", FileMode.CreateNew, FileAccess.Write);
                            fs.Write(b, 0, b.Length);
                            fs.Close();

                        }
                        catch (Exception e)
                        {
                            Debug.Print("Error creating link: " + e.Message);
                            Prompt("Could not create link.\n" + sPXE, sAppName, PromptType.OKOnly);
                            return false;
                        }
                    }
                }
            }

            // Service Present
            if (i == 1)
            {
                if (!IsServiceInstalled(sPXE))
                    AppLoader.RegisterService(this, (StartMode)i, sPXE, null);
            }

            Prompt(sAppName + " has been installed.", Product, PromptType.OKOnly);


            File.Delete(filename);
            MyDrives.FlushFileSystems();
            return true;
        }

        public bool IsServiceInstalled(string filename)
        {
            if (!File.Exists(filename))
                return false;

            string sServ = MyDrives.RootDirectory + Resources.GetString(Resources.StringResources.ServicePath);
            if (!File.Exists(sServ))
                return false;

            string[] Services = new string(UTF8Encoding.UTF8.GetChars(File.ReadAllBytes(sServ))).Split('\n');
            string[] Parts;

            filename = filename.ToLower();
            for (int i = 0; i < Services.Length; i++)
            {
                Parts = Services[i].Split('|');
                if (Parts[0].ToLower() == filename)
                    return true;
            }
            return false;
        }

        public string OpenFile()
        {
            return OpenFile("Open File", "", null);
        }

        public string OpenFile(string Title)
        {
            return OpenFile(Title, "", null);
        }

        public string OpenFile(string Title, string StartDirectory)
        {
            return OpenFile(Title, StartDirectory, null);
        }

        public string OpenFile(string Title, string StartDirectory, string[] Extensions)
        {
            _IsSave = false;
            CreateDialog(Title, StartDirectory, Extensions);
            return _inputResult;
        }

        public PromptResult Prompt(string Message, string Title, PromptType Type)
        {
            int w, h, x, y, i;

            // First figure out how wide to make the window
            FontManager.Arial.ComputeExtent(Title, out w, out h);
            size sz = FontManager.ComputeExtentEx(FontManager.Arial, Message);
            if (sz.Width > w) w = sz.Width;

            // Add border
            w += 16;

            // Make sure we meet the Minimum width
            // CommandButtons are 60px wide each
            // 4px spaces are put between CommandButtons
            // 4px spaces are put between CommandButtons & borders
            // Borders are 4px each
            switch (Type)
            {
                case PromptType.OKOnly:
                    // 4px border + 4px space + 60 CommandButton + 4px space + 4px border
                    if (w < 76) w = 76;
                    break;
                case PromptType.OKCancel:
                case PromptType.YesNo:
                    // 4px border + 4px space + 60 CommandButton + 4px space + 60 CommandButton + 4px space + 4px border
                    if (w < 140) w = 140;
                    break;
                case PromptType.YesNoCancel:
                    // 4px border + 4px space + 60 CommandButton + 4px space + 60 CommandButton + 4px space + 60 CommandButton + 4px space + 4px border
                    if (w < 204) w = 204;
                    break;
            }

            // Now make sure we're within the screen width
            if (w > AppearanceManager.ScreenWidth - 8) w = AppearanceManager.ScreenWidth - 8;

            // Get the height of the 2 strings combined
            // We already have the title height in h
            // Remove 16px for 2 borders + 2 spacers (4+4+4+4)
            FontManager.Arial.ComputeTextInRect(Message, out y, out i, w - 16);
            h = h + i;

            // Add borders and spacing 49px (4 border + 2 space + title + 2 space + 4 border = 4 space + text height + 4 space + CommandButton height + 4 space + 4 border)
            h = h + 69;

            // Make sure we're still on the screen
            if (h > AppearanceManager.ScreenHeight - 8) h = AppearanceManager.ScreenHeight - 8;

            // Center the window
            x = (int)(((float)AppearanceManager.ScreenWidth / 2) - ((float)w / 2));
            y = (int)(((float)AppearanceManager.ScreenHeight / 2) - ((float)h / 2));

            // We're ready to create the PromptWindow
            Form pw = new Form(this, Colors.LightGray, x, y, w, h, true, true, Form.WindowType.fullborder);
            pw.AddChild(new Label(Title, Color.White, 6, 5, w - 10, FontManager.Arial.Height));
            pw.AddChild(new Label(Message, Color.Black, 8, 5 + FontManager.Arial.Height + 6, w - 16, h - 69 - FontManager.Arial.Height));

            switch (Type)
            {
                case PromptType.OKOnly:
                    x = (int)(((float)w / 2) - 30);
                    CommandButton btnOK = new CommandButton("OK", FontManager.Arial, Color.Black, x, h - 33, 60, 25);
                    btnOK.tapEvent += delegate { Modal_CommandButtonClicked(PromptResult.OK); };
                    pw.AddChild(btnOK);
                    break;
                case PromptType.OKCancel:
                case PromptType.YesNo:
                    x = (int)(((float)w / 2) - 62);
                    CommandButton btnYes = new CommandButton("Yes", FontManager.Arial, Color.Black, x, h - 33, 60, 25);
                    btnYes.tapEvent += delegate { Modal_CommandButtonClicked(PromptResult.Yes); };
                    pw.AddChild(btnYes);
                    x += 64;
                    CommandButton btnNo = new CommandButton("No", FontManager.Arial, Color.Black, x, h - 33, 60, 25);
                    btnNo.tapEvent += delegate { Modal_CommandButtonClicked(PromptResult.No); };
                    pw.AddChild(btnNo);
                    break;
                case PromptType.YesNoCancel:
                    x = (int)(((float)w / 2) - 92);
                    CommandButton btn1 = new CommandButton("Yes", FontManager.Arial, Color.Black, x, h - 33, 60, 25);
                    btn1.tapEvent += delegate { Modal_CommandButtonClicked(PromptResult.Yes); };
                    pw.AddChild(btn1);
                    x += 64;
                    CommandButton btn2 = new CommandButton("No", FontManager.Arial, Color.Black, x, h - 33, 60, 25);
                    btn2.tapEvent += delegate { Modal_CommandButtonClicked(PromptResult.No); };
                    pw.AddChild(btn2);
                    x += 64;
                    CommandButton btn3 = new CommandButton("Cancel", FontManager.Arial, Color.Black, x, h - 33, 60, 25);
                    btn3.tapEvent += delegate { Modal_CommandButtonClicked(PromptResult.Cancel); };
                    pw.AddChild(btn3);
                    break;
            }

            // Set the Active Form (without rendering)
            Form prv = _ActiveForm;
            _ActiveForm = pw;

            // Copy out the current buffer
            Bitmap buffer2 = new Bitmap(AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight);
            buffer2.DrawImage(0, 0, _buffer, 0, 0, buffer2.Width, buffer2.Height);

            // Draw Prompt
            _buffer.DrawRectangle(Color.Black, 0, 0, 0, buffer2.Width, buffer2.Height, 0, 0, Color.Black, 0, 0, Color.Black, 0, 0, 100);
            _buffer.Flush();
            pw.Render();

            // Start Modal Touch Collection
            myTouch = new Thread(myTouch_Tick);
            myTouch.Priority = ThreadPriority.Highest;
            myTouch.Start();

            ModalBlock();

            // Restore Screen
            _ActiveForm = prv;
            _buffer.SetClippingRectangle(0, 0, buffer2.Width, buffer2.Height);
            _buffer.DrawImage(0, 0, buffer2, 0, 0, buffer2.Width, buffer2.Height);
            _buffer.Flush();

            // Return result
            return _promptResult;
        }

        public PromptResult PromptDialog(string Message, string Details, string Title, PromptType Type)
        {
            int w = AppearanceManager.ScreenWidth - 10;
            int h = AppearanceManager.ScreenHeight - 10;
            int x, lw, lh, y;

            // We're ready to create the PromptWindow
            Form pw = new Form(this, Colors.LightGray, 5, 5, w, h, true, true, Form.WindowType.fullborder);
            pw.AddChild(new Label(Title, Color.White, 6, 5, w - 10, FontManager.Arial.Height));
            
            // Determine height of message label
            FontManager.Arial.ComputeTextInRect(Message, out lw, out lh, w - 16);
            Label lblMessage = new Label(Message, Color.Black, 8, 5 + FontManager.Arial.Height + 6, w - 16, lh);
            pw.AddChild(lblMessage);

            // Create Textbox
            y = lblMessage.Top + lblMessage.Height + 4;
            lh = h - y - 35;
            Textbox txtDetails = new Textbox(Details, 8, y, w - 16, lh);
            txtDetails.ReadOnly = true;
            txtDetails.Multiline = true;
            pw.AddChild(txtDetails);

            switch (Type)
            {
                case PromptType.OKOnly:
                    x = (int)(((float)w / 2) - 30);
                    CommandButton btnOK = new CommandButton("OK", FontManager.Arial, Color.Black, x, h - 33, 60, 25);
                    btnOK.tapEvent += delegate { Modal_CommandButtonClicked(PromptResult.OK); };
                    pw.AddChild(btnOK);
                    break;
                case PromptType.OKCancel:
                case PromptType.YesNo:
                    x = (int)(((float)w / 2) - 62);
                    CommandButton btnYes = new CommandButton("Yes", FontManager.Arial, Color.Black, x, h - 33, 60, 25);
                    btnYes.tapEvent += delegate { Modal_CommandButtonClicked(PromptResult.Yes); };
                    pw.AddChild(btnYes);
                    x += 64;
                    CommandButton btnNo = new CommandButton("No", FontManager.Arial, Color.Black, x, h - 33, 60, 25);
                    btnNo.tapEvent += delegate { Modal_CommandButtonClicked(PromptResult.No); };
                    pw.AddChild(btnNo);
                    break;
                case PromptType.YesNoCancel:
                    x = (int)(((float)w / 2) - 92);
                    CommandButton btn1 = new CommandButton("Yes", FontManager.Arial, Color.Black, x, h - 33, 60, 25);
                    btn1.tapEvent += delegate { Modal_CommandButtonClicked(PromptResult.Yes); };
                    pw.AddChild(btn1);
                    x += 64;
                    CommandButton btn2 = new CommandButton("No", FontManager.Arial, Color.Black, x, h - 33, 60, 25);
                    btn2.tapEvent += delegate { Modal_CommandButtonClicked(PromptResult.No); };
                    pw.AddChild(btn2);
                    x += 64;
                    CommandButton btn3 = new CommandButton("Cancel", FontManager.Arial, Color.Black, x, h - 33, 60, 25);
                    btn3.tapEvent += delegate { Modal_CommandButtonClicked(PromptResult.Cancel); };
                    pw.AddChild(btn3);
                    break;
            }

            // Set the Active Form (without rendering)
            Form prv = _ActiveForm;
            _ActiveForm = pw;

            // Copy out the current buffer
            Bitmap buffer2 = new Bitmap(AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight);
            buffer2.DrawImage(0, 0, _buffer, 0, 0, buffer2.Width, buffer2.Height);

            // Draw Prompt
            _buffer.DrawRectangle(Color.Black, 0, 0, 0, buffer2.Width, buffer2.Height, 0, 0, Color.Black, 0, 0, Color.Black, 0, 0, 100);
            _buffer.Flush();
            pw.Render();

            // Start Modal Touch Collection
            myTouch = new Thread(myTouch_Tick);
            myTouch.Priority = ThreadPriority.Highest;
            myTouch.Start();

            ModalBlock();

            // Restore Screen
            _ActiveForm = prv;
            _buffer.SetClippingRectangle(0, 0, buffer2.Width, buffer2.Height);
            _buffer.DrawImage(0, 0, buffer2, 0, 0, buffer2.Width, buffer2.Height);
            _buffer.Flush();

            // Return result
            return _promptResult;
        }

        public void QuitApp(ApplicationKey AppKey)
        {
            PyxisApplication PA;

            // Find the right Application
            for (int i = 0; i < _runningApps.Count; i++)
            {
                PA = (PyxisApplication)_runningApps[i];
                if (PA.Key == AppKey)
                {
                    _KeyQuitter = AppKey;
                    _PAQuitter = PA;
                    _launcher = new Thread(QuitAppSafely);
                    _launcher.Priority = ThreadPriority.Normal;
                    _launcher.Start();

                    return;
                }
            }

            throw new Exception("Invalid Application Key");
        }

        public void RefreshScreen()
        {
            desktop.Render(true);
        }

        public string SaveFile(bool CheckFileExistance)
        {
            return SaveFile("Save File", CheckFileExistance, "", null);
        }

        public string SaveFile(string Title, bool CheckFileExistance)
        {
            return SaveFile(Title, CheckFileExistance, "", null);            
        }

        public string SaveFile(string Title, bool CheckFileExistance, string StartDirectory)
        {
            return SaveFile(Title, CheckFileExistance, StartDirectory, null);
        }
        
        public string SaveFile(string Title, bool CheckFileExistance, string StartDirectory, string[] Extensions)
        {
            _IsSave = true;
            _CheckExist = CheckFileExistance;
            CreateDialog(Title, StartDirectory, Extensions);
            return _inputResult;
        }

        public Color SelectColor(Color InitialColor = 0)
        {
            // Copy out the current buffer
            Bitmap buffer2 = new Bitmap(AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight);
            buffer2.DrawImage(0, 0, _buffer, 0, 0, buffer2.Width, buffer2.Height);

            // Create the Form
            Form frmSel = new Form(this, Colors.LightGray, AppearanceManager.ScreenWidth / 2 - 140, AppearanceManager.ScreenHeight / 2 - 111, 280, 223, true, true, Form.WindowType.fullborder);
            frmSel.AddChild(new Label("Select Color", Color.White, 6, 5, frmSel.Width - 10, FontManager.Arial.Height));

            frmSel.AddChild(new Label("Red:", 191, 28));
            frmSel.AddChild(new Label("Green:", 191, 52));
            frmSel.AddChild(new Label("Blue:", 191, 76));

            NumericUpDown numRed = new NumericUpDown(230, 26, 40, 0, 255);
            frmSel.AddChild(numRed);
            NumericUpDown numGreen = new NumericUpDown(230, 50, 40, 0, 255);
            frmSel.AddChild(numGreen);
            NumericUpDown numBlue = new NumericUpDown(230, 74, 40, 0, 255);
            frmSel.AddChild(numBlue);

            Picturebox pbPrv = new Picturebox(null, 191, 100, 79, 15);
            pbPrv.BorderStyle = BorderStyle.BorderFlat;
            pbPrv.Background = InitialColor;
            frmSel.AddChild(pbPrv);

            numRed.ValueChanged += new OnValueChanged((object sender, int value) => SelColorNum(numRed, numBlue, numGreen, pbPrv));
            numBlue.ValueChanged += new OnValueChanged((object sender, int value) => SelColorNum(numRed, numBlue, numGreen, pbPrv));
            numGreen.ValueChanged += new OnValueChanged((object sender, int value) => SelColorNum(numRed, numBlue, numGreen, pbPrv));

            Picturebox pbCol = new Picturebox(EmbeddedResourceManager.GetBitmapByName("colors"), 8, 26);
            pbCol.Width = 179;
            pbCol.Height = 191;
            pbCol.BorderStyle = BorderStyle.BorderFlat;
            pbCol.tapEvent += new OnTap((object sender, point e) => SelColor(pbCol, e, numRed, numBlue, numGreen));
            frmSel.AddChild(pbCol);

            CommandButton cmdSel = new CommandButton("Select", 193, 161);
            cmdSel.tapEvent += new OnTap((object sender, point e) => Color_CommandClicked(pbPrv.Background));
            frmSel.AddChild(cmdSel);

            CommandButton cmdCancel = new CommandButton("Cancel", 193, 190);
            cmdCancel.tapEvent += new OnTap((object sender, point e) => Color_CommandClicked(InitialColor));
            frmSel.AddChild(cmdCancel);

            // Set the Active Form (without rendering)
            Form prv = _ActiveForm;
            _ActiveForm = frmSel;

            // Draw Prompt
            _buffer.DrawRectangle(Color.Black, 0, 0, 0, buffer2.Width, buffer2.Height, 0, 0, Color.Black, 0, 0, Color.Black, 0, 0, 100);
            _buffer.Flush();
            frmSel.Render();

            // Start Modal Touch Collection
            myTouch = new Thread(myTouch_Tick);
            myTouch.Priority = ThreadPriority.Highest;
            myTouch.Start();

            ModalBlock();

            // Restore Screen
            _ActiveForm = prv;
            _buffer.DrawImage(0, 0, buffer2, 0, 0, buffer2.Width, buffer2.Height);
            _buffer.Flush();

            // Return result
            return _colorResult;
        }

        public string SelectDirectory()
        {
            return SelectDirectory("Select Directory");
        }

        public string SelectDirectory(string Title)
        {
            // Copy out the current buffer
            Bitmap buffer2 = new Bitmap(AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight);
            buffer2.DrawImage(0, 0, _buffer, 0, 0, buffer2.Width, buffer2.Height);

            // Create the Form
            Form frmSel = new Form(this, Colors.LightGray, 10, 10, AppearanceManager.ScreenWidth - 20, AppearanceManager.ScreenHeight - 20, true, true, Form.WindowType.fullborder);
            frmSel.AddChild(new Label(Title, Color.White, 6, 5, frmSel.Width - 10, FontManager.Arial.Height));
            Treeview trv = new Treeview(8, 5 + FontManager.Arial.Height + 6, frmSel.Width - 16, frmSel.Height - 29 - 11 - FontManager.Arial.Height - 4);

            // Initial Treeview Loading
            for (int i = 0; i < MyDrives.AvailableDrives.Length; i++)
            {
                TreeviewNode node = new TreeviewNode(MyDrives.AvailableDrives[i].RootName);
                node.Tag = node.Text + "\\";
                if (node.Text == MyDrives.RootDirectory)
                {
                    string[] dirs = Directory.GetDirectories(node.Text);
                    for (int j = 0; j < dirs.Length; j++)
                    {
                        TreeviewNode subnode = new TreeviewNode(dirs[j].Substring(dirs[j].LastIndexOf("\\") + 1));
                        if (Directory.GetDirectories(dirs[j]).Length > 0) subnode.AddNode(new TreeviewNode(string.Empty));
                        subnode.Tag = dirs[j];
                        node.AddNode(subnode);
                    }
                }
                else
                {
                    if (Directory.GetDirectories(node.Text).Length > 0)
                    {
                        node.AddNode(new TreeviewNode(string.Empty));
                    }
                }
                trv.AddNode(node);
            }
            trv.NodeExpanded += new OnNodeExpanded(SelNodeExpanded);
            frmSel.AddChild(trv);

            CommandButton cmdOK = new CommandButton("OK", frmSel.Width - 160, frmSel.Height - 31);
            cmdOK.tapEvent += new OnTap((object sender, point e) =>  Select_CommandButtonClicked(trv.SelectedNode));
            frmSel.AddChild(cmdOK);

            CommandButton cmdCancel = new CommandButton("Cancel", frmSel.Width - 82, cmdOK.Y);
            cmdCancel.tapEvent += new OnTap((object sender, point e) =>  Input_CommandButtonClicked(""));
            frmSel.AddChild(cmdCancel);

            CommandButton cmdNew = new CommandButton("New Folder", trv.X, cmdOK.Y);
            cmdNew.tapEvent += new OnTap((object sender, point e) => Select_NewFolder(trv));
            frmSel.AddChild(cmdNew);

            // Set the Active Form (without rendering)
            Form prv = _ActiveForm;
            _ActiveForm = frmSel;

            // Draw Prompt
            _buffer.DrawRectangle(Color.Black, 0, 0, 0, buffer2.Width, buffer2.Height, 0, 0, Color.Black, 0, 0, Color.Black, 0, 0, 100);
            _buffer.Flush();
            frmSel.Render();

            // Start Modal Touch Collection
            myTouch = new Thread(myTouch_Tick);
            myTouch.Priority = ThreadPriority.Highest;
            myTouch.Start();

            ModalBlock();

            // Restore Screen
            _ActiveForm = prv;
            _buffer.DrawImage(0, 0, buffer2, 0, 0, buffer2.Width, buffer2.Height);
            _buffer.Flush();

            // Return result
            return _inputResult;
        }

        public string SelectDrive()
        {
            return SelectDrive("Select Drive");
        }

        public string SelectDrive(string Title)
        {

            // Copy out the current buffer
            Bitmap buffer2 = new Bitmap(AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight);
            buffer2.DrawImage(0, 0, _buffer, 0, 0, buffer2.Width, buffer2.Height);

            // Create the Form
            Form frmSel = new Form(this, Colors.LightGray, 10, 10, AppearanceManager.ScreenWidth - 20, AppearanceManager.ScreenHeight - 20, true, true, Form.WindowType.fullborder);
            frmSel.AddChild(new Label(Title, Color.White, 6, 5, frmSel.Width - 10, FontManager.Arial.Height));
            Treeview trv = new Treeview(8, 5 + FontManager.Arial.Height + 6, frmSel.Width - 16, frmSel.Height - 29 - 11 - FontManager.Arial.Height - 4);

            // Treeview Loading
            for (int i = 0; i < MyDrives.AvailableDrives.Length; i++)
            {
                TreeviewNode node = new TreeviewNode(MyDrives.AvailableDrives[i].RootName);
                node.Tag = node.Text + "\\";
                trv.AddNode(node);
            }
            frmSel.AddChild(trv);

            CommandButton cmdOK = new CommandButton("OK", frmSel.Width - 160, frmSel.Height - 31);
            cmdOK.tapEvent += new OnTap((object sender, point e) =>  Select_CommandButtonClicked(trv.SelectedNode));
            frmSel.AddChild(cmdOK);

            CommandButton cmdCancel = new CommandButton("Cancel", frmSel.Width - 82, cmdOK.Y);
            cmdCancel.tapEvent += new OnTap((object sender, point e) =>  Input_CommandButtonClicked(""));
            frmSel.AddChild(cmdCancel);

            // Set the Active Form (without rendering)
            Form prv = _ActiveForm;
            _ActiveForm = frmSel;

            // Draw Prompt
            _buffer.DrawRectangle(Color.Black, 0, 0, 0, buffer2.Width, buffer2.Height, 0, 0, Color.Black, 0, 0, Color.Black, 0, 0, 100);
            _buffer.Flush();
            frmSel.Render();

            // Start Modal Touch Collection
            myTouch = new Thread(myTouch_Tick);
            myTouch.Priority = ThreadPriority.Highest;
            myTouch.Start();

            ModalBlock();

            // Restore Screen
            _ActiveForm = prv;
            _buffer.DrawImage(0, 0, buffer2, 0, 0, buffer2.Width, buffer2.Height);
            _buffer.Flush();

            // Return result
            return _inputResult;
        }

        public int SelectionDialog(string Title, string[] values)
        {
            int x = 4;
            int w = AppearanceManager.ScreenWidth - 8;
            int y = 26;
            int h = AppearanceManager.ScreenHeight - 30;

            Form frmSel = new Form(this, Colors.LightGray, x, y, w, h, true, true, Form.WindowType.fullborder);

            // Window Title
            Label lblTitle = new Label(Title, Color.White, 6, 5, w - 10, FontManager.Arial.Height);
            frmSel.AddChild(lblTitle);

            // Adjust Y
            y = lblTitle.Y + lblTitle.Height + 4;

            Listbox lst1 = new Listbox(4, y, frmSel.Width - 8, frmSel.Height - y - 33);
            for (int i = 0; i < values.Length; i++)
                lst1.AddItem(values[i]);
            frmSel.AddChild(lst1);

            CommandButton cmdSelect = new CommandButton("Select", 4, frmSel.Height - 29);
            cmdSelect.tapEvent += new OnTap((object sender, point e) => Sel_CommandClick(lst1.SelectedIndex));
            frmSel.AddChild(cmdSelect);

            CommandButton cmdCancel = new CommandButton("Cancel", frmSel.Width - 79, frmSel.Height - 29);
            cmdCancel.tapEvent += new OnTap((object sender, point e) => Sel_CommandClick(-1));
            frmSel.AddChild(cmdCancel);

            // Copy out the current buffer
            Bitmap buffer2 = new Bitmap(AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight);
            buffer2.DrawImage(0, 0, _buffer, 0, 0, buffer2.Width, buffer2.Height);

            // Set the Active Form (without rendering)
            Form prv = _ActiveForm;
            _ActiveForm = frmSel;

            // Draw Prompt
            _buffer.DrawRectangle(Color.Black, 0, 0, 0, buffer2.Width, buffer2.Height, 0, 0, Color.Black, 0, 0, Color.Black, 0, 0, 100);
            _buffer.Flush();
            frmSel.Render();

            // Start Modal Touch Collection
            myTouch = new Thread(myTouch_Tick);
            myTouch.Priority = ThreadPriority.Highest;
            myTouch.Start();

            ModalBlock();

            // Restore Screen
            _ActiveForm = prv;
            _buffer.DrawImage(0, 0, buffer2, 0, 0, buffer2.Width, buffer2.Height);
            _buffer.Flush();

            return _selResult;
        }

        public void SelectScreenSize()
        {
            // Only allow default domain to set the boot logo
            if (AppDomain.CurrentDomain.FriendlyName != "default") return;

            // Don't set logo in emulator
            //if (SystemInfo.SystemID.SKU == 3) return;

            // Create Window
            Form frmSize = new Form(this, Colors.LightGray, 0, 0, AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight, true, true);

            // Add Label
            Label lbl1 = new Label("Please select your screen size.", 4, 4, AppearanceManager.ScreenWidth - 8, 20);
            frmSize.AddChild(lbl1);

            // Add Listbox
            Listbox lst1 = new Listbox(4, 24, AppearanceManager.ScreenWidth - 8, 98);
            lst1.AddItem("320x240");
            lst1.AddItem("480x272");
            lst1.AddItem("800x480");
            frmSize.AddChild(lst1);

            // Add CommandButton
            CommandButton btnDone = new CommandButton("Set Resolution and Reboot", 4, 126, AppearanceManager.ScreenWidth - 8, 25);
            btnDone.tapEvent += new OnTap((object sender, point e) =>  selRes(lst1));
            frmSize.AddChild(btnDone);

            // Activate Form
            ActiveForm = frmSize;

            // Do not leave this method until it forces a reboot
            Thread.Sleep(-1);
        }

        public void SetDateTime(DateTime NewDateTime)
        {
            if (DeviceManager.ActiveDevice != DeviceType.Emulator)
            {
                try
                {
                    RealTimeClock.SetTime(NewDateTime);
                }
                catch (Exception) { }
            }
            Utility.SetLocalTime(NewDateTime);
            OnDateTimeChanged();
        }

        public void SetTimeZoneOffset(int OffsetInMinutes)
        {
            TimeService.SetTimeZoneOffset(OffsetInMinutes);
            OnDateTimeChanged();
        }

        public void ShowAbout()
        {
            if (DeviceManager.ActiveDevice == DeviceType.Emulator)
                Prompt(PyxisAPI.Product + "\nVersion: " +
                       PyxisAPI.Version + "\nScreen Size: " + AppearanceManager.ScreenWidth + "x" + AppearanceManager.ScreenHeight +
                       "\nCopyright: " + PyxisAPI.Copyright +
                       "\nFree RAM: " + PyxisAPI.FreeRAM(true) +
                       "\nCustom Heap Size: " + Configuration.Heap.CurrentCustomHeapSize + " of " + Configuration.Heap.TotalHeapSize +
                       "\n\nSpecial Thanks: GHI Electronics",
                       "About " + PyxisAPI.Product, PromptType.OKOnly);
            else
                Prompt(PyxisAPI.Product + "\nVersion: " +
                       PyxisAPI.Version + "\nScreen Size: " + AppearanceManager.ScreenWidth + "x" + AppearanceManager.ScreenHeight +
                       "\nCopyright: " + PyxisAPI.Copyright +
                       "\nFree RAM: " + PyxisAPI.FreeRAM(true) +
                       "\nCustom Heap Size: " + Configuration.Heap.CurrentCustomHeapSize + " of " + Configuration.Heap.TotalHeapSize +
                       "\n\nSpecial Thanks: GHI Electronics",
                       "About " + PyxisAPI.Product, PromptType.OKOnly);
        }

        public void ShowContextMenu(ContextMenu contextmenu, point e)
        {
            ShowContextMenu(contextmenu, e.X, (e.Y < 22) ? 22 : e.Y);
        }

        public void ShowContextMenu(ContextMenu contextmenu, int x, int y)
        {
            if (_ActiveCM != null)
                RefreshScreen();

            _ActiveCM = contextmenu;
            contextmenu.Show(this, x, y);
        }

        public void StartApplication(string path)
        {
            StartApplication(path, Path.GetFileName(path), null);
        }

        public void StartApplication(string path, string[] parameters)
        {
            StartApplication(path, Path.GetFileName(path), parameters);
        }

        public void StartApplication(string path, string title)
        {
            StartApplication(path, title, null);
        }

        public void StartApplication(string path, string title, string[] parameters)
        {
            switch (path)
            {
                case "-1":
                    AppStore.Show(this);
                    break;
                case "-2":
                    FileFinder.Show(this);
                    break;
                case "-3":
                    PictureViewer.Show(this, null);
                    break;
                case "-4":
                    SettingsWin.Show(this);
                    break;
                default:
                    _sRelay = path;
                    _sTitle = title;
                    _sParams = parameters;
                    _launcher = new Thread(ApplicationLaunchThread);
                    _launcher.Priority = ThreadPriority.AboveNormal;
                    _launcher.Start();
                    break;
            }
        }

        public void StartService(ServiceKey ServiceKey)
        {
            PyxisService ps;
            for (int i = 0; i < _services.Count; i++)
            {
                ps = (PyxisService)_services[i];
                if (ps.Key == ServiceKey)
                {
                    if (!ps.Info.IsRunning)
                    {
                        ModalMessage MM = new ModalMessage("Starting '" + ps.Info.Name + "'...", this);
                        MM.Start(false);
                        _services[i] = AppLoader.StartRegisteredService(this, ps, null);
                        MM.Stop();
                        return;
                    }
                    else
                        throw new Exception("Service is already running");
                }
            }

            throw new Exception("Service not found");
        }

        public void StopService(ServiceKey ServiceKey)
        {
            PyxisService ps;
            for (int i = 0; i < _services.Count; i++)
            {
                ps = (PyxisService)_services[i];
                if (ps.Key == ServiceKey)
                {
                    if (ps.Info.IsRunning)
                    {
                        ModalMessage MM = new ModalMessage("Stopping '" + ps.Info.Name + "'...", this);
                        MM.Start(false);
                        AppDomain.Unload(ps.Key.AppDomain);
                        ps.Info.IsRunning = false;
                        ps.Key = new ServiceKey(-1, null, ps.Key.ServicePath);
                        _services[i] = ps;
                        MM.Stop();
                        return;
                    }
                    else
                        throw new Exception("Service is not running");
                }
            }

            throw new Exception("Service not found");
        }

        public static string StringReplace(string Source, string ToFind, string ReplaceWith)
        {
            int i;
            int iStart = 0;

            if (ToFind == string.Empty)
                return Source;

            while (true)
            {
                i = Source.IndexOf(ToFind, iStart);
                if (i < 0) break;

                if (i > 0)
                {
                    Source = Source.Substring(0, i) + ReplaceWith + Source.Substring(i + ToFind.Length);
                }
                else
                {
                    Source = ReplaceWith + Source.Substring(i + ToFind.Length);
                }

                iStart = i + ReplaceWith.Length;
            }
            return Source;
        }

        #endregion

        #region Internal Methods

        internal void ActivateApplication(AppStartup Startup, ApplicationKey AppKey)
        {
            // Create App Menus
            MenuItem mnuApp = new MenuItem(Startup.title);
            if (Startup.menus != null)
            {
                for (int i = 0; i < Startup.menus.Length; i++)
                    mnuApp.AddItem(Startup.menus[i]);
            }

            // Update Desktop Menus
            if (desktop._mnu.count > 1) desktop._mnu.remove(1);
            desktop._mnu.add(mnuApp);

            MenuItem mnuSwitchToApp = new MenuItem(Startup.title);
            mnuSwitchToApp.tapEvent += new MenuItemTap((object sender) => switchTo(Startup));
            mnuSwitchToApp.Tag = AppKey;
            desktop.mnuRun.AddItem(mnuSwitchToApp);
            desktop.mnuRun.Enabled = true;

            MenuItem mnuQuitApp = new MenuItem(Startup.title);
            mnuQuitApp.tapEvent += new MenuItemTap((object sender) => QuitApp(AppKey));
            mnuQuitApp.Tag = AppKey;
            desktop.mnuForce.AddItem(mnuQuitApp);
            desktop.mnuForce.Enabled = true;

            // Add Startup Info to Application
            PyxisApplication PA;
            for (int i = 0; i < _runningApps.Count; i++)
            {
                PA = (PyxisApplication)_runningApps[i];
                if (PA.Key == AppKey)
                {
                    PA.StartupInfo = Startup;
                    // Formless Applications ARE allowed
                    if (PA.StartupInfo.startupForm != null)
                        ActiveForm = PA.StartupInfo.startupForm;
                    break;
                }
            }

        }

        internal byte[] CreateLink(byte[] data, string path) 
        {
            // Get Title & Icon
            AppIcon AI = AppLoader.GetApplicationIcon(data);

            // Create byte array
            byte[] bTemp;
            byte[] bData = new byte[AI.title.Length + path.Length + 2 + AI.icon.Length];

            // Insert Path
            bData[0] = (byte)path.Length;
            bTemp = Encoding.UTF8.GetBytes(path);
            Array.Copy(bTemp, 0, bData, 1, bTemp.Length);

            // Insert Title
            bData[path.Length + 1] = (byte)AI.title.Length;
            bTemp = Encoding.UTF8.GetBytes(AI.title);
            Array.Copy(bTemp, 0, bData, path.Length + 2, bTemp.Length);

            // Copy Icon
            Array.Copy(AI.icon, 0, bData, path.Length + AI.title.Length + 2, AI.icon.Length);

            return bData;
        }

        internal int GetTimeZoneMinutes(int iRes)
        {
            if (iRes == -1)
                return 0;
            if (iRes == 0)
                return -720;	// - 12hrs
            if (iRes <= 2)
                return -660;	// - 11hrs
            if (iRes == 3)
                return -600;	// - 10hrs
            if (iRes == 4)
                return -540;	// - 9hrs
            if (iRes <= 6)
                return -480;	// - 8hrs
            if (iRes <= 9)
                return -420;	// - 7hrs
            if (iRes <= 13)
                return -360;	// - 6hrs
            if (iRes <= 16)
                return -300;	// - 5hrs
            if (iRes == 17)
                return -270;	// - 4.5hrs
            if (iRes <= 22)
                return -240;	// - 4hrs
            if (iRes == 23)
                return -210;	// - 3.3hrs
            if (iRes <= 28)
                return -180;	// - 3hrs
            if (iRes <= 30)
                return -120;	// - 2hrs
            if (iRes <= 32)
                return -60;		// - 1hr
            if (iRes <= 36)
                return 0;
            if (iRes <= 42)
                return 60;		// + 1hr
            if (iRes <= 51)
                return 120;		// + 2hrs
            if (iRes <= 55)
                return 180;		// + 3hrs
            if (iRes == 56)
                return 210;		// + 3.5hrs
            if (iRes <= 61)
                return 240;		// + 4hrs
            if (iRes == 62)
                return 270;		// + 4.5hrs
            if (iRes <= 65)
                return 300;		// + 5hrs
            if (iRes <= 67)
                return 330;		// + 5.5hrs
            if (iRes == 68)
                return 345;		// + 5.75hrs
            if (iRes <= 71)
                return 360;		// + 6hrs
            if (iRes == 72)
                return 390;		// + 6.5hrs
            if (iRes <= 74)
                return 420;		// + 7hrs
            if (iRes <= 80)
                return 480;		// + 8hrs
            if (iRes <= 83)
                return 540;		// + 9hrs
            if (iRes <= 85)
                return 570;		// + 9.5hrs
            if (iRes <= 90)
                return 600;		// + 10hrs
            if (iRes <= 92)
                return 660;		// + 11hrs
            if (iRes <= 95)
                return 720;		// + 12hrs

            return 750;		// + 12.5hrs
        }

        internal bool PrepHDD(PyxisDrive Drive)
        {
            string sRoot = Drive.RootName + "\\";
            string sName;

            byte[] b;
            FileStream iFile;

            Form frmInstall = new Form(this, Colors.LightGray, 0, 0, AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight, true, true);
            Label lblStatus = new Label("Creating Directories...", 24, AppearanceManager.ScreenHeight / 2 - 60);
            frmInstall.AddChild(lblStatus);

            Progressbar prog1 = new Progressbar(20, AppearanceManager.ScreenHeight / 2 - 27, AppearanceManager.ScreenWidth - 40, 10);
            frmInstall.AddChild(prog1);

            ActiveForm = frmInstall;

            try
            {
                // Create Directories
                string[] paths = new string[] { "apps\\", "documents\\", "documents\\temp\\", "pictures\\", "pyxis\\", "pyxis\\desktop\\", "pyxis\\fonts", "pyxis\\wallpapers\\", "pyxis\\system\\" };
                prog1.Maximum = paths.Length;
                for (int i = 0; i < paths.Length; i++)
                {
                    if (!Directory.Exists(sRoot + paths[i])) 
                        Directory.CreateDirectory(sRoot + paths[i]);
                    prog1.Value++;
                }
                Drive.VolumeInfo.FlushAll();

                // Copy Files
                lblStatus.Text = "Creating files...";
                string[] sPapers = new string[] { "bluestripes", "greenstripes", "grunge", "pyxis2gray" };
                prog1.Value = 0;
                prog1.Maximum = sPapers.Length + 2;

                for (int i = 0; i < sPapers.Length; i++)
                {
                    b = EmbeddedResourceManager.GetWallpaperByName(sPapers[i]);
                    sName = sRoot + "pyxis\\wallpapers\\" + sPapers[i] + ".jpg";
                    if (File.Exists(sName)) 
                        File.Delete(sName);
                    iFile = new FileStream(sName, FileMode.CreateNew);
                    iFile.Write(b, 0, b.Length);
                    iFile.Close();
                    prog1.Value++;
                }

                b = Resources.GetBytes(Resources.BinaryResources.click);
                sName = sRoot + "pyxis\\system\\click.mp3";
                if (File.Exists(sName))
                    File.Delete(sName);
                iFile = new FileStream(sName, FileMode.CreateNew);
                iFile.Write(b, 0, b.Length);
                iFile.Close();
                prog1.Value++;

                // Create Desktop XML
                sName = sRoot + "pyxis\\system\\desktop.xml";
                if (File.Exists(sName)) File.Delete(sName);
                iFile = new FileStream(sName, FileMode.CreateNew);
                XmlWriter xmlwrite = XmlWriter.Create(iFile);
                xmlwrite.WriteProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\"");
                xmlwrite.WriteComment("Pyxis 2.0 Desktop Settings");
                xmlwrite.WriteStartElement("Settings");

                xmlwrite.WriteStartElement("Appearance");

                xmlwrite.WriteStartElement("Backcolor");
                xmlwrite.WriteString("131,131,131");
                xmlwrite.WriteEndElement();

                xmlwrite.WriteStartElement("Wallpaper");
                xmlwrite.WriteString("grunge.jpg");
                xmlwrite.WriteEndElement();

                xmlwrite.WriteStartElement("WallpaperMode");
                xmlwrite.WriteString("3");
                xmlwrite.WriteEndElement();

                xmlwrite.WriteStartElement("ShowAppStore");
                xmlwrite.WriteString("1");
                xmlwrite.WriteEndElement();

                xmlwrite.WriteStartElement("ShowFileFinder");
                xmlwrite.WriteString("1");
                xmlwrite.WriteEndElement();

                xmlwrite.WriteStartElement("ShowPictureViewer");
                xmlwrite.WriteString("1");
                xmlwrite.WriteEndElement();

                xmlwrite.WriteStartElement("ShowSettings");
                xmlwrite.WriteString("1");
                xmlwrite.WriteEndElement();

                xmlwrite.WriteEndElement();

                xmlwrite.WriteEndElement();
                xmlwrite.Flush();
                xmlwrite.Close();
                iFile.Close();
                Drive.VolumeInfo.FlushAll();

            }
            catch (Exception)
            {
                return false;
            }

            return true;
        }

        internal void QuitAppQuitely(ApplicationKey AppKey)
        {
            PyxisApplication PA;

            // Find the right Application
            for (int i = 0; i < _runningApps.Count; i++)
            {
                PA = (PyxisApplication)_runningApps[i];
                if (PA.Key == AppKey)
                {
                    // Unload the application domain
                    AppDomain.Unload(PA.Domain);

                    // Remove Application entry
                    _runningApps.Remove(PA);

                    return;
                }
            }
        }

        internal void RaiseUnhandledTouch(bool Down, point e)
        {
            OnUnhandledTouch(Down, e);
        }

        internal void RegisterForm(Form Form, AppDomain AD)
        {

            // Find App
            PyxisApplication PA;
            for (int i = 0; i < _runningApps.Count; i++)
            {
                PA = (PyxisApplication)_runningApps[i];
                if (PA.Domain == AD)
                {
                    PA.Forms.Add(Form);
                    return;
                }
            }

        }

        internal void RegisterService(ServiceKey ServiceKey, ServiceInfo ServiceInfo)
        {

            PyxisService PS = new PyxisService(ServiceKey, ServiceInfo);
            _services.Add(PS);

            // Add Service to list (if not existant)
            string sPath = MyDrives.RootDirectory + Resources.GetString(Resources.StringResources.ServicePath);
            if (!IsServiceInstalled(ServiceInfo.Location))
            {
                int i = SelectionDialog(ServiceInfo.Name + " has a service to install; what mode would you like to install it as?", new string[] { "Manual", "Automatic", "Disabled" });
                string sDir = string.Empty;

                if (File.Exists(sPath))
                {
                    sDir = new string(UTF8Encoding.UTF8.GetChars(File.ReadAllBytes(Resources.GetString(Resources.StringResources.ServicePath))));
                    if (sDir.LastIndexOf("\n") != sDir.Length - 1)
                        sDir += "\n";

                    File.Delete(sPath);
                }

                sDir += ServiceInfo.Location + "|" + i + "|\n";

                FileStream fs = new FileStream(sPath, FileMode.CreateNew, FileAccess.Write);
                byte[] b = UTF8Encoding.UTF8.GetBytes(sDir);
                fs.Write(b, 0, b.Length);
                fs.Close();

            }
            
            // Get rid of id & domain if we haven't started the service
            // Trying to unload right away would cause threading errors
            // So we'll use a new thread and let it sleep a few milliseconds
            if (ServiceInfo.StartMode != StartMode.Automatic)
            {
                _KeyQuitter = ServiceKey;
                new Thread(ServiceRegisterExit).Start();
            }
        }

        internal void SetModalState(bool Modal)
        {
            OnModalStateChanged(Modal);
        }

        internal void UpdateService(PyxisService PS)
        {
            PyxisService lPS;
            for (int i = 0; i < _services.Count; i++)
            {
                lPS = (PyxisService)_services[i];
                if (PS.Key == lPS.Key)
                {
                    _services[i] = PS;
                    return;
                }
            }
        }

        #endregion

        #region Private Methods

        private void ApplicationLaunchThread()
        {
            int w, h, x, y, i;
            string Message = "Launching '" + _sTitle + "'...";
            Form prv = _ActiveForm;
            Bitmap buffer2 = new Bitmap(AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight);

            // Copy out the current buffer
            buffer2.DrawImage(0, 0, _buffer, 0, 0, buffer2.Width, buffer2.Height);

            // First figure out how wide to make the window
            FontManager.Arial.ComputeExtent(Product, out w, out h);
            size sz = FontManager.ComputeExtentEx(FontManager.Arial, Message);
            if (sz.Width > w) w = sz.Width;

            // Add border
            w += 16;

            // Check Mimimum Width
            if (w < 76) w = 76;

            // Now make sure we're within the screen width
            if (w > AppearanceManager.ScreenWidth - 8) w = AppearanceManager.ScreenWidth - 8;

            // Get the height of the 2 strings combined
            // We already have the title height in h
            // Remove 16px for 2 borders + 2 spacers (4+4+4+4)
            FontManager.Arial.ComputeTextInRect(Message, out y, out i, w - 16);
            h = h + i;

            // Add borders and spacing 49px (4 border + 2 space + title + 2 space + 4 border = 4 space + text height + 4 space + CommandButton height + 4 space + 4 border)
            h = h + 20;

            // Make sure we're still on the screen
            if (h > AppearanceManager.ScreenHeight - 8) h = AppearanceManager.ScreenHeight - 8;

            // Center the window
            x = (int)(((float)AppearanceManager.ScreenWidth / 2) - ((float)w / 2));
            y = (int)(((float)AppearanceManager.ScreenHeight / 2) - ((float)h / 2));

            // We're ready to create the window
            Form frmLaunch = new Form(this, Colors.LightGray, x, y, w, h, true, true, Form.WindowType.fullborder);
            frmLaunch.AddChild(new Label(Product, Color.White, 6, 5, w - 10, FontManager.Arial.Height));
            frmLaunch.AddChild(new Label(Message, Color.Black, 8, 5 + FontManager.Arial.Height + 6, w - 16, h - 20 - FontManager.Arial.Height));

            // Set the Active Form (without rendering)
            _ActiveForm = frmLaunch;

            // Draw Prompt
            _buffer.DrawRectangle(Color.Black, 0, 0, 0, buffer2.Width, buffer2.Height, 0, 0, Color.Black, 0, 0, Color.Black, 0, 0, 100);
            _buffer.Flush();
            frmLaunch.Render();

            if (!AppLoader.LaunchApplication(this, _sRelay, _sParams))
            {
                 _buffer.SetClippingRectangle(0, 0, buffer2.Width, buffer2.Height);
                _buffer.DrawImage(0, 0, buffer2, 0, 0, buffer2.Width, buffer2.Height);
                _ActiveForm = prv;
                _buffer.Flush();         
            }

        }

        private void CreateDialog(string Title, string StartDirectory, string[] Extensions)
        {
            int x = 4;
            int w = AppearanceManager.ScreenWidth - 8;
            int y = 26;
            int h = AppearanceManager.ScreenHeight - 30;
            Label lblTitle;
            Filebox fbox;
            Combobox cboDrives;
            CommandButton btnCancel, btnOpenSave;
            Textbox txtFile;
            Treeview trvDirectories;

            Form frmSel = new Form(this, Colors.LightGray, x, y, w, h, true, true, Form.WindowType.fullborder);

            // Window Title
            lblTitle = new Label(Title, Color.White, 6, 5, w - 10, FontManager.Arial.Height);
            frmSel.AddChild(lblTitle);

            // Adjust Y
            y = lblTitle.Y + lblTitle.Height + 4;

            // Drive Selection
            frmSel.AddChild(new Label("Drive:", Colors.Black, FontManager.ArialBold, 8, y + 2, 34, 16));
            cboDrives = new Combobox(48, y, 86, 20);
            for (int i = 0; i < DM.DriveRoots.Length; i++)
                cboDrives.AddItem(DM.DriveRoots[i]);
            frmSel.AddChild(cboDrives);

            // Directory Selection
            trvDirectories = new Treeview(8, y + 22, 126, frmSel.Height - y - 54);
            try
            {
                LoadTreeview(trvDirectories, DM.DriveRoots[0]);
            }
            catch (Exception) { }
            frmSel.AddChild(trvDirectories);

            // File Selection
            fbox = new Filebox(StartDirectory, Extensions, 138, y, frmSel.Width - 148, frmSel.Height - y - 32);
            frmSel.AddChild(fbox);

            // Action CommandButtons
            btnCancel = new CommandButton("Cancel", frmSel.Width - 85, frmSel.Height - 31);
            frmSel.AddChild(btnCancel);
            btnOpenSave = new CommandButton((_IsSave) ? "Save" : "Open", btnCancel.X - 79, btnCancel.Y);
            btnOpenSave.Enabled = false;
            frmSel.AddChild(btnOpenSave);

            // Filename
            frmSel.AddChild(new Label("File:", Colors.Black, FontManager.ArialBold, 8, btnCancel.Y + 5, 28, 16));
            txtFile = new Textbox("", 32, btnCancel.Y + 3, btnOpenSave.X - 36, 20);
            frmSel.AddChild(txtFile);

            // Copy out the current buffer
            Bitmap buffer2 = new Bitmap(AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight);
            buffer2.DrawImage(0, 0, _buffer, 0, 0, buffer2.Width, buffer2.Height);

            // Hook Events
            cboDrives.SelectedIndexChanged += new OnSelectedIndexChange((object sender, int index) => ChangeDrives(trvDirectories, cboDrives, fbox, buffer2, frmSel));
            trvDirectories.NodeExpanded += new OnNodeExpanded(trvDirectories_NodeExpanded);
            trvDirectories.NodeTapped += new OnNodeTap((TreeviewNode node, point e) => fbox.Path = FileManager.NormalizeDirectory((string)node.Tag));
            fbox.SelectedFileChanged += new OnSelectedFileChanged((object sender, string file) => ChangeFile(fbox, txtFile));
            txtFile.TextChanged += new OnTextChanged((object sender) => FileChanged(txtFile, btnOpenSave));
            btnCancel.tapEvent += new OnTap((object sender, point e) =>  Input_CommandButtonClicked(string.Empty));
            btnOpenSave.tapEvent += new OnTap((object sender, point e) =>  OpenSave(fbox, txtFile, buffer2, frmSel));

            // Set the Active Form (without rendering)
            Form prv = _ActiveForm;
            _ActiveForm = frmSel;

            // Draw Prompt
            _buffer.DrawRectangle(Color.Black, 0, 0, 0, buffer2.Width, buffer2.Height, 0, 0, Color.Black, 0, 0, Color.Black, 0, 0, 100);
            _buffer.Flush();
            frmSel.Render();

            // Start Modal Touch Collection
            myTouch = new Thread(myTouch_Tick);
            myTouch.Priority = ThreadPriority.Highest;
            myTouch.Start();

            ModalBlock();

            // Restore Screen
            _ActiveForm = prv;
            _buffer.DrawImage(0, 0, buffer2, 0, 0, buffer2.Width, buffer2.Height);
            _buffer.Flush();
        }

        private bool DomainExists(AppDomain domain)
        {
            for (int i = 0; i < _runningApps.Count; i++)
            {
                PyxisApplication PA = (PyxisApplication)_runningApps[i];
                if (PA.Domain == domain) return true;
            }
            return false;
        }

        private void LoadServices()
        {
            string sPath = MyDrives.RootDirectory + Resources.GetString(Resources.StringResources.ServicePath);
            string[] sItems, sParts, sParams;
            byte[] b;

            if (!File.Exists(sPath))
                return;

            FileStream iFile = new FileStream(sPath, FileMode.Open, FileAccess.Read);
            b = new byte[new FileInfo(sPath).Length];
            iFile.Read(b, 0, b.Length);
            iFile.Close();

            if (b.Length == 0)
                return;

            // Allow system time to start properly
            Thread.Sleep(2000);

            sItems = new string(UTF8Encoding.UTF8.GetChars(b)).Split('\n');
            for (int i = 0; i < sItems.Length; i++)
                if (sItems[i] != String.Empty)
                {
                    sParts = sItems[i].Split('|');
                    sParams = sParts[2].Split('`');
                    AppLoader.RegisterService(this, (StartMode)int.Parse(sParts[1]), sParts[0], sParams);
                    Thread.Sleep(40);   // Wait between launches for cleanup
                }
        }

        private void LoadTreeview(Treeview trv, string fullpath)
        {
            fullpath = FileManager.NormalizeDirectory(fullpath);
            trv.Suspended = true;
            trv.ClearNodes();
            string[] folders = Directory.GetDirectories(fullpath);

            TreeviewNode root = new TreeviewNode(fullpath);
            root.Tag = fullpath;

            try
            {
                for (int i = 0; i < folders.Length; i++)
                {
                    TreeviewNode myNode = new TreeviewNode(folders[i].Substring(folders[i].LastIndexOf("\\") + 1));
                    if (Directory.GetDirectories(folders[i]).Length > 0) myNode.AddNode(new TreeviewNode(string.Empty));
                    myNode.Tag = folders[i];
                    root.AddNode(myNode);
                }
            }
            catch (Exception) { }

            if (root.Length > 0) root.Expanded = true;
            trv.AddNode(root);
            trv.Suspended = false;
        }

        private void Modal_CommandButtonClicked(PromptResult pr)
        {
            _promptResult = pr;
            _activeBlock.Set();
        }

        private void Color_CommandClicked(Color cr)
        {
            _colorResult = cr;
            _activeBlock.Set();
        }

        private void Sel_CommandClick(int value)
        {
            _selResult = value;
            _activeBlock.Set();
        }

        private void Input_CommandButtonClicked(string ir)
        {
            _inputResult = ir;
            _activeBlock.Set();
        }

        private void InputFixVKeyboard(Bitmap backupBuffer)
        {
            // Re-Draw Prompt
            _buffer.DrawImage(0, 0, backupBuffer, 0, 0, backupBuffer.Width, backupBuffer.Height);
            _buffer.DrawRectangle(Color.Black, 0, 0, 0, backupBuffer.Width, backupBuffer.Height, 0, 0, Color.Black, 0, 0, Color.Black, 0, 0, 100);
            _buffer.Flush();
            _ActiveForm.Render();
        }

        private void myTouch_Tick()
        {
            int _x, _y;
            int _lastX = 0;
            int _lastY = 0;
            int x = 0;
            int y = 0;
            bool _penDown = false;

            while (true)
            {
                // Check the Pen
                TouchCollectorConfiguration.GetLastTouchPoint(ref x, ref y);

                if (x != 1022 && x > 0 || y != 1022 && y > 0)
                {
                    _x = x;
                    _y = y;

                    // Pen down
                    if (_penDown)
                    {
                        // We already know about it
                        if (System.Math.Abs(_lastX - x) + System.Math.Abs(_lastY - y) > 10)
                        {
                            _lastX = x;
                            _lastY = y;
                            _ActiveForm.TouchMove(null, new point(_lastX, _lastY));
                        }
                    }
                    else
                    {
                        // Pen Down
                        _penDown = true;
                        _lastX = x;
                        _lastY = y;

                        _ActiveForm.TouchDown(null, new point(_lastX, _lastY));
                    }
                }
                else
                {
                    if (_penDown)
                    {
                        // Pen Tapped
                        _penDown = false;

                        _ActiveForm.TouchUp(null, new point(_lastX, _lastY));
                    }
                }

                Thread.Sleep(5);
            }
        }

        private void myCalibrate_Tick()
        {
            point[] calPoints = null;
            int currentCalPoint = 0;
            short[] sx = null;
            short[] sy = null;
            short[] cx = null;
            short[] cy = null;
            int i = 0;
            int x = 0;
            int y = 0;
            TouchEventArgs e;

            // Remove Active Form (w/o rendering)
            Form prv = _ActiveForm;
            _ActiveForm = null;

            // Copy out the current buffer
            Bitmap buffer2 = new Bitmap(AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight);
            buffer2.DrawImage(0, 0, _buffer, 0, 0, buffer2.Width, buffer2.Height);

            // Ask the touch system how many points are needed to 
            // calibrate.
            int calibrationPointCount = 0;
            Touch.ActiveTouchPanel.GetCalibrationPointCount(ref calibrationPointCount);

            // Create the calibration point array.
            calPoints = new point[calibrationPointCount];
            sx = new short[calibrationPointCount];
            sy = new short[calibrationPointCount];
            cx = new short[calibrationPointCount];
            cy = new short[calibrationPointCount];

            // Get the points for calibration.
            for (i = 0; i < calibrationPointCount; i++)
            {
                Touch.ActiveTouchPanel.GetCalibrationPoint(i, ref x, ref y);
                calPoints[i].X = x;
                calPoints[i].Y = y;

                sx[i] = (short)x;
                sy[i] = (short)y;
            }

            // Start the calibration process.
            currentCalPoint = 0;
            Touch.ActiveTouchPanel.StartCalibration();
            IM.Pause();

            // Get Calibration Points
            while (true)
            {
                _buffer.DrawRectangle(Color.Black, 0, 0, 0, buffer2.Width, buffer2.Height, 0, 0, Colors.Blue, 0, 0, Colors.Blue, 0, 0, 256);
                DrawCrossHair(calPoints[currentCalPoint].X, calPoints[currentCalPoint].Y);
                _buffer.Flush();
                e = IM.getTouchPoint();
                ++currentCalPoint;
                cx[currentCalPoint - 1] = (short)e.location.X;
                cy[currentCalPoint - 1] = (short)e.location.Y;
                e = IM.getTouchPoint();    // Don't forget about the touch up

                if (currentCalPoint == calPoints.Length)
                {
                    // The last point has been reached , so set the 
                    // calibration.
                    Touch.ActiveTouchPanel.SetCalibration(calPoints.Length, sx, sy, cx, cy);
                    SettingsManager.SaveLCDCalibration(calPoints.Length, sx, sy, cx, cy);
                    break;
                }

                Thread.Sleep(1000);

            }

            // Restore Screen
            _ActiveForm = prv;
            _buffer.DrawImage(0, 0, buffer2, 0, 0, buffer2.Width, buffer2.Height);
            _buffer.Flush();

            // Restore Input State
            IM.Resume();
            if (_activeBlock != null)
                _activeBlock.Set();
        }

        /// <summary>
        /// Helper function to draw a crosshair.
        /// </summary>
        /// <param name="dc"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        private void DrawCrossHair(int x, int y)
        {
            _buffer.DrawLine(Colors.Red, 1, x - 10, y, x - 2, y);
            _buffer.DrawLine(Colors.Red, 1, x + 10, y, x + 2, y);
            _buffer.DrawLine(Colors.Red, 1, x, y - 10, x, y - 2);
            _buffer.DrawLine(Colors.Red, 1, x, y + 10, x, y + 2);
        }

        private void InstallUpdates()
        {
            string sNotes = new string(UTF8Encoding.UTF8.GetChars(MyNetwork.DownloadURL("http://www.skewworks.com/updatenotes.php?id=PyxisOS2")));
            if (PromptDialog("Updates are available; would you like to download them now?\nDoing so will call all applications to close.", sNotes, "System Updates", PromptType.YesNo) == PromptResult.No) return;

            // Prep Window
            int y = (AppearanceManager.ScreenHeight / 2) - 26;
            Form frmDownload = new Form(this, Colors.LightGray, 10, y, AppearanceManager.ScreenWidth - 20, 51, true, true, Form.WindowType.container);
            Label lblStatus = new Label("Exiting Applications...", 8, 8);
            Progressbar progDownload = new Progressbar(8, 28, frmDownload.Width - 16, 15);
            frmDownload.AddChild(lblStatus);
            frmDownload.AddChild(progDownload);
            progDownload.Maximum = _runningApps.Count;

            ScreenBuffer.DrawRectangle(Color.Black, 0, 0, 0, AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight, 0, 0, Colors.Black, 0, 0, Colors.Black, 0, 0, 100);
            ScreenBuffer.Flush();
            ActiveForm = frmDownload;

            // Quit Apps
            while (_runningApps.Count > 0)
            {
                PyxisApplication PA = (PyxisApplication)_runningApps[0];
                QuitApp(PA.Key);
                progDownload.Value++;
            }

            // Download File(s)
            byte[] bFiles = MyNetwork.DownloadURL("http://www.skewworks.com/updatefiles.php?id=PyxisOS2");
            string[] files = new string(UTF8Encoding.UTF8.GetChars(bFiles)).Split('|');
            progDownload.Value = 0;
            progDownload.Maximum = files.Length;
            bool bDownload;
            for (int i = 0; i < files.Length; i++)
            {
                bDownload = true;
                if (files[i] == "ER_CONFIG" && DeviceManager.ActiveDevice != DeviceType.ChipworkX) bDownload = false;
                if (files[i] == "ER_FLASH" && DeviceManager.ActiveDevice != DeviceType.ChipworkX) bDownload = false;

                if (files[i] == "CLR.HEX" && DeviceManager.ActiveDevice != DeviceType.Cobra) bDownload = false;
                if (files[i] == "CLR2.HEX" && DeviceManager.ActiveDevice != DeviceType.Cobra) bDownload = false;
                if (files[i] == "Config.HEX" && DeviceManager.ActiveDevice != DeviceType.Cobra) bDownload = false;

                if (bDownload)
                {
                    progDownload.Value = 0;
                    lblStatus.Text = "Downloading '" + files[i] + "'";
                    Debug.Print(">>" + files[i] + MyNetwork.DownloadFile("http://www.skewworks.com/downloads/updates/pyxis2/" + files[i], MyDrives.RootDirectory + "\\pyxis\\system\\" + files[i], progDownload, 5120));
                    Debug.GC(true);
                }
            }

            // Prompt and restart
            Prompt(PyxisAPI.Product + " must now reboot to apply updates.", "System Updates", PromptType.OKOnly);
            SystemUpdate.AccessBootloader();
        }

        private void ModalBlock()
        {
            SetModalState(true);
            ManualResetEvent localBlocker = new ManualResetEvent(false);

            if (_activeBlock != null)
                _blockers.Add(_activeBlock);

            _activeBlock = localBlocker;

            // Wait for Result
            localBlocker.Reset();
            while (!localBlocker.WaitOne(1000, false))
            {
                ;
            }

            // Unblock
            ReleaseBlock();

            // End Modal Touch
            if (_blockers.Count == 0)
            {
                try { myTouch.Suspend(); }
                catch (Exception) { }
                myTouch = null;
            }
        }

        private void ReleaseBlock()
        {
            if (_blockers.Count > 0)
            {
                _activeBlock = (ManualResetEvent)_blockers[0];
                _blockers.Remove(_activeBlock);
            }
            else
            {
                _activeBlock = null;
            }
            SetModalState(false);
        }

        private void QuitAppSafely()
        {

            // Unload the application domain
            AppDomain.Unload(_PAQuitter.Domain);

            // Remove Menus
            for (int j = 0; j < desktop.mnuForce.ItemCount; j++)
            {
                if (desktop.mnuForce.Item(j).Tag == _KeyQuitter)
                {
                    desktop.mnuForce.RemoveItem(desktop.mnuForce.Item(j));
                    break;
                }
            }
            if (desktop.mnuForce.ItemCount == 0) desktop.mnuForce.Enabled = false;
            for (int j = 0; j < desktop.mnuRun.ItemCount; j++)
            {
                if (desktop.mnuRun.Item(j).Tag == _KeyQuitter)
                {
                    desktop.mnuRun.RemoveItem(desktop.mnuRun.Item(j));
                    break;
                }
            }
            if (desktop.mnuRun.ItemCount == 0) desktop.mnuRun.Enabled = false;

            // Revert to desktop
            DisplayDesktop();

            // Remove Application entry
            _runningApps.Remove(_PAQuitter);

            // Collect Garbage
            Debug.GC(true);

        }

        private void ServiceRegisterExit()
        {
            Thread.Sleep(10);
            ServiceKey SK = (ServiceKey)_KeyQuitter;
            PyxisService PS;

            for (int i = 0; i < _services.Count; i++)
            {
                PS = (PyxisService)_services[i];
                if (PS.Key == SK)
                {
                    AppDomain.Unload(PS.Key.AppDomain);
                    PS.Key = new ServiceKey(-1, null, PS.Key.ServicePath);
                    _services[i] = PS;
                    return;
                }
            }

        }

        private void SelColor(Picturebox pb, point e, NumericUpDown numRed, NumericUpDown numBlue, NumericUpDown numGreen)
        {
            Color c = pb.Image.GetPixel(e.X - 1, e.Y - 1);
            numRed.Value = ColorUtility.GetRValue(c);
            numBlue.Value = ColorUtility.GetBValue(c);
            numGreen.Value = ColorUtility.GetGValue(c);
        }

        private void SelColorNum(NumericUpDown numRed, NumericUpDown numBlue, NumericUpDown numGreen, Picturebox pbPreview)
        {
            pbPreview.Background = ColorUtility.ColorFromRGB((byte)numRed.Value, (byte)numGreen.Value, (byte)numBlue.Value);
        }

        private void Select_CommandButtonClicked(TreeviewNode node)
        {
            if (node == null)
            {
                _inputResult = "";
            }
            else
            {
                _inputResult = FileManager.NormalizeDirectory((string)node.Tag);
            }

            _activeBlock.Set();
        }

        private void SelNodeExpanded(object sender, TreeviewNode node)
        {
            if (node.Length == 1 && node.Node(0).Text == string.Empty)
            {
                Treeview tv = (Treeview)sender;
                string[] folders;

                tv.Suspended = true;
                node.ClearNodes();

                folders = Directory.GetDirectories((string)node.Tag);
                for (int i = 0; i < folders.Length; i++)
                {
                    TreeviewNode myNode = new TreeviewNode(folders[i].Substring(folders[i].LastIndexOf("\\") + 1));
                    if (Directory.GetDirectories(folders[i]).Length > 0) myNode.AddNode(new TreeviewNode(string.Empty));
                    myNode.Tag = folders[i];
                    node.AddNode(myNode);
                }
                node.Expanded = true;
                tv.Suspended = false;
            }
        }

        private void selRes(Listbox lst1)
        {
            switch (lst1.SelectedIndex)
            {
                case 0:     // 320x240
                    SettingsManager.screen320x240();
                    break;
                case 1:     // 480x272
                    SettingsManager.screen480x272();
                    break;
                case 2:     // 800x480
                    SettingsManager.screen800x480();
                    break;
            }

            // Update settings
            SettingsManager.RequireLCDCalibration();


            // Reboot the device
            if (SystemInfo.SystemID.SKU == 3)
            {
                Debug.Print("Inside Emulator: Soft rebooting...");
                PowerState.RebootDevice(true);
            }

            PowerState.RebootDevice(false);
        }

        private void switchTo(AppStartup app)
        {
            // Create App Menus
            MenuItem mnuApp = new MenuItem(app.title);
            if (app.menus != null)
            {
                for (int i = 0; i < app.menus.Length; i++)
                {
                    mnuApp.AddItem(app.menus[i]);
                }
            }

            // Update Desktop Menus
            if (desktop._mnu.count > 1) desktop._mnu.remove(1);
            desktop._mnu.add(mnuApp);

            // Activate Form
            ActiveForm = app.startupForm;
        }

        private void UpdateThread()
        {
            // Make sure we have a primary HDD
            if (MyDrives.RootDirectory == "\\" && SystemInfo.SystemID.SKU != 3)
            {
                if (!_IsSave) Prompt("Cannot updates without a drive prepared for " + PyxisAPI.Product + ".", "Alert", PromptType.OKOnly);
                return;
            }

            // Make sure we're connected to the net
            if (!MyNetwork.NetworkConnected)
            {
                if (!_IsSave) Prompt("No network connection is available!", "Error", PromptType.OKOnly);
                return;
            }

            byte[] data = MyNetwork.DownloadURL("http://www.skewworks.com/updates.php?id=PyxisOS2");

            if (data == null)
            {
                if (!_IsSave) Prompt("Could not contact update server.", "Alert", PromptType.OKOnly);
                return;
            }
            else if (data.Length == 0)
            {
                if (!_IsSave) Prompt("Could not locate update inFormation.", "Alert", PromptType.OKOnly);
                return;
            }

            int iDate = int.Parse(new string(Encoding.UTF8.GetChars(data)));
            if (iDate <= PyxisAPI.BuildDate)
            {
                if (!_IsSave) Prompt("Your system is up-to-date.", PyxisAPI.Product, PromptType.OKOnly);
                return;
            }

            if (_IsSave)
            {
                MenuTrayIcon mti = new MenuTrayIcon(null, new Image32(Resources.GetBytes(Resources.BinaryResources.update)));
                mti.tapEvent += new OnTap(UpdateIconTap);
                mti.TapHold += new OnTapHold(UpdateIconTap);

                desktop._mnu._tray.AddIcon(mti);

                return;
            }

            InstallUpdates();
        }

        private void UpdateIconTap(object sender, point e)
        {
            ContextMenu CM = new ContextMenu();
            ContextMenuItem cmiInstall = new ContextMenuItem("Install Updates");
            cmiInstall.tapEvent += new GUI.Controls.MenuItemTap((object sender2) => InstallUpdates());
            CM.Add(cmiInstall);
            ShowContextMenu(CM, e);
        }

        #endregion

        #region File Dialog Events

        /// <summary>
        /// Updates each of the main objects to reflect a change in target drives
        /// Called when cboDrives selected index is changed
        /// </summary>
        /// <param name="trv">Target Treeview</param>
        /// <param name="cbo">Calling Combobox</param>
        private void ChangeDrives(Treeview trv, Combobox cbo, Filebox fbox, Bitmap bmp, Form frm)
        {
            LoadTreeview(trv, cbo.SelectedItem);
            fbox.Path = cbo.SelectedItem;
            _ActiveForm = frm;
            bmp.Flush();
            frm.Render();
        }

        /// <summary>
        /// Called when the Filebox's selected file is changed
        /// </summary>
        /// <param name="sender">Sending Filebox</param>
        /// <param name="path">Path of selected file</param>
        private void ChangeFile(Filebox fbox, Textbox txt)
        {
            txt.Text = Path.GetFileName(fbox.SelectedFile);
        }

        /// <summary>
        /// Checks for depth loading on node expansion
        /// </summary>
        /// <param name="sender">Node parent (aka trewview)</param>
        /// <param name="node">Node being expanded</param>
        private void trvDirectories_NodeExpanded(object sender, TreeviewNode node)
        {
            if (node.Length == 1 && node.Node(0).Text == string.Empty)
            {
                Treeview tv = (Treeview)sender;
                string[] folders;

                tv.Suspended = true;
                node.ClearNodes();

                folders = Directory.GetDirectories((string)node.Tag);
                for (int i = 0; i < folders.Length; i++)
                {
                    TreeviewNode myNode = new TreeviewNode(folders[i].Substring(folders[i].LastIndexOf("\\") + 1));
                    if (Directory.GetDirectories(folders[i]).Length > 0) myNode.AddNode(new TreeviewNode(string.Empty));
                    myNode.Tag = folders[i];
                    node.AddNode(myNode);
                }
                node.Expanded = true;
                tv.Suspended = false;
            }
        }

        private void FileChanged(Textbox txt, CommandButton cmd)
        {
            cmd.Enabled = (txt.Text == string.Empty) ? false : true;
        }

        private void OpenSave(Filebox fbox, Textbox txt, Bitmap bmp, Form frm)
        {
            string path;

            // Get the full path
            if (txt.Text.IndexOf("\\") < 0)
            {
                path = FileManager.NormalizeDirectory(fbox.Path) + txt.Text;
            }
            else
            {
                path = txt.Text;
            }

            if (_IsSave)
            {
                if (_CheckExist && File.Exists(path))
                {
                    PromptResult PR = Prompt("Are you sure you wish to replace '" + path + "'?", "Question", PromptType.YesNoCancel);
                    if (PR == PromptResult.Cancel) path = "";
                    if (PR == PromptResult.No) return;
                }
            }
            else
            {
                if (!File.Exists(path))
                {
                    Prompt(path + "\nFile does not exist.\nCheck filename and try again.", "Error", PromptType.OKOnly);
                    return;
                }
            }

            _inputResult = path;
            _activeBlock.Set();
        }

        private void Select_NewFolder(Treeview trv)
        {
            TreeviewNode node = trv.Node(0);
            TreeviewNode snde;

            string sRes = Inputbox("Choose a name for your new folder:", Product);
            if (sRes == string.Empty) return;

            snde = new TreeviewNode(sRes);
            if (trv.SelectedNode != null)
            {
                sRes = string.Concat(FileManager.NormalizeDirectory((string)trv.SelectedNode.Tag), sRes);
                node = trv.SelectedNode;
            }
            else
            {
                sRes = string.Concat(MyDrives.RootDirectory, sRes);
            }
            snde.Tag = sRes;

            try
            {
                Directory.CreateDirectory(sRes);
                node.AddNode(snde);
                node.Expanded = true;
                trv.SelectedNode = snde;
            }
            catch (Exception e)
            {
                Prompt("Could not create " + sRes + "\n" + e.Message, Product, PromptType.OKOnly);
            }

            

        }

        #endregion

    }
}
